﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Collections.Specialized;
using System.Web.Routing;
using System.Web.Mvc;
using System.Xml;
using System.Configuration;
using System.IO;
using System.Web.Hosting;
using System.Collections;
using System.Xml.Linq;
using System.Globalization;

namespace Tencent.OA.App
{
    /// <summary>
    /// 从 <see cref="System.Web.StaticSiteMapProvider"/> 类派生，用于asp.net MVC的站点地图提供程序。
    /// <b>MvcSiteMapProvider</b> 类基于文件扩展名为 .sitemap 的 XML 文件生成站点地图树。
    /// </summary>
    /// <remarks>
    /// MvcSiteMapProvider 类从遵循已知架构的 XML 文件加载站点地图数据。
    /// 站点地图数据用 &lt;mvcSiteMap&gt; 标记括起来，并包含嵌套的 &lt;mvcSiteMapNode&gt; 标记。
    /// SiteMapProvider 和 MvcSiteMapProvider 类对站点地图数据的结构施加了两个约束：
    /// <list type="bullet">
    ///     <item>
    ///         <description>只能存在一个mvcSiteMap根节点。</description>
    ///     </item>
    ///     <item>
    ///         <description>对于未指定 area 的 MvcSiteMapNode 对象，其 controller 和 action 在SiteMapNode层级的范围内必须是唯一的;
    ///         对于指定了 area 的节点，则在该 area 范围内都必须是唯一的。</description>
    ///     </item>
    ///     <item>
    ///         <description>对于各层次的MvcSiteMapNode 节点上，如果生成的url重复时则需显示指定子节点的url。</description>
    ///     </item>
    ///     <item>
    ///         <description>当父节点和子节点公用一个Controller和Action，而又指定了不同的url时，
    ///         则该父节点不应显示指定Action的值，以供提供程序正确地查找执行当前请求的Action和MvcSiteMapNode。</description>
    ///     </item>
    /// </list>
    /// </remarks>
    public class MvcSiteMapProvider : StaticSiteMapProvider, IDisposable
    {
        // 定义.sitemap文件节点的名称
        private const string SITEMAP_ROOT_NODENAME = "mvcSiteMap";
        private const string SITEMAP_NODENAME = "mvcSiteMapNode";

        // 指示是否已经调用Initalize方法进行初始化站点地图，
        // 确保只进行一次初始化工作
        private bool m_initialized;

        // 默认的sitemap文件路径
        private string m_siteMapFile = "~/mvc.sitemap";
        private readonly string m_siteMapNamespace = "http://schemas.microsoft.com/AspNetMvc/SiteMap-File-2.0";

        private MvcSiteMapNode m_rootNode = null;
        private StringCollection m_parentSiteMapFileCollection;
        private RouteCollection m_routeCollection;

        // 文件监视器，监视.sitemap文件的更改
        //
        private FileSystemWatcher m_fileHandler;

        // 定义用于同步锁定的对象
        private readonly object m_syncLock = new object();

        #region Properties

        /// <summary>
        /// 获取或设置控制器类型的解析器。
        /// </summary>
        public IControllerTypeResolver ControllerTypeResolver { get; set; }

        /// <summary>
        /// 获取或设置通过MvcSiteMapNode节点包含的controllerName、actionName信息生成Url的解析器。
        /// </summary>
        /// <value>The site map node URL resolver.</value>
        public ISiteMapNodeUrlResolver SiteMapNodeUrlResolver { get; set; }

        /// <summary>
        /// 获取或设置SiteMapNode节点是否有权限访问的提供程序。
        /// </summary>
        public ISiteMapNodeVisibilityProvider SiteMapNodeVisibilityProvider { get; set; }

        /// <summary>
        /// 获取或设置用于获取操作方法的参数的实现程序。
        /// </summary>
        public IActionMethodParameterResolver ActionMethodParameterResolver { get; set; }

        /// <summary>
        /// 获取一个值，该值指示是否已经调用Initalize方法进行初始化站点地图数据。
        /// </summary>
        public virtual bool IsInitialized
        {
            get { return this.m_initialized; }
        }

        protected virtual XNamespace SiteMapNamespace
        {
            get { return this.m_siteMapNamespace; }
        }

        /// <summary>
        /// 获取或设置为 ASP.NET 路由操作提供路由的集合。
        /// </summary>
        public RouteCollection RouteCollection
        {
            get
            {
                if (this.m_routeCollection == null)
                {
                    this.m_routeCollection = RouteTable.Routes;
                }
                return this.m_routeCollection;
            }
            set
            {
                this.m_routeCollection = value;
            }
        }

        /// <summary>
        /// 获取表示当前请求页的 <see cref="System.Web.SiteMapNode"/> 对象。
        /// </summary>
        /// <value>
        /// 表示当前请求页的 <see cref="System.Web.SiteMapNode"/>；否则，如果没有找到或不能为当前用户返回 <see cref="System.Web.SiteMapNode"/>，则为 null。
        /// </value>
        public override SiteMapNode CurrentNode
        {
            get
            {
                SiteMapNode node = base.CurrentNode;
                if(node == null)
                    node = this.FindSiteMapNode(HttpContext.Current);
                return node;
            }
        }

        /// <summary>
        /// 获取站点地图的根节点。
        /// </summary>
        public override SiteMapNode RootNode
        {
            get
            {
                return GetRootNodeCore();
            }
        }

        /// <summary>
        /// 获取当前提供程序层次结构中的根 System.Web.SiteMapProvider 对象。
        /// </summary>
        /// <value>
        /// 一个 System.Web.SiteMapProvider，表示当前提供程序所属的提供程序层次结构中的顶级站点地图提供程序。
        /// </value>
        public override SiteMapProvider RootProvider
        {
            get
            {
                if (this.ParentProvider != null)
                {
                    return ParentProvider.RootProvider;
                }
                else
                {
                    return this;
                }
            }
        }

        #endregion Properties

        /// <summary>
        /// 从 XML 格式的.sitemap文件加载站点地图信息，并在内存中生成它。
        /// （重写 StaticSiteMapProvider.BuildSiteMap()。）
        /// </summary>
        /// <returns></returns>
        public override SiteMapNode BuildSiteMap()
        {
            if (this.m_rootNode != null)
                return this.m_rootNode;

            if (!this.m_initialized)
            {
                throw new InvalidOperationException("MvcSiteMapProvider 未正确地初始化。");
            }
            if (string.IsNullOrEmpty(this.m_siteMapFile) || !this.m_siteMapFile.EndsWith(".sitemap"))
            {
                throw new ArgumentException("未指定 siteMapFile 文件路径或该路径位于应用程序的当前目录结构之外，或 siteMapFile 所指定的文件的扩展名不是 .sitemap。");
            }
            if (!File.Exists(HttpContext.Current.Server.MapPath(this.m_siteMapFile)))
                throw new InvalidOperationException("siteMapFile 所指定的文件不存在。");

            this.m_parentSiteMapFileCollection = new StringCollection();
            MvcSiteMapProvider parentProvider = this.ParentProvider as MvcSiteMapProvider;
            if ((parentProvider != null) && (parentProvider.m_parentSiteMapFileCollection != null))
            {
                // 确保当前的siteMap文件未被其他Provider引用
                if (parentProvider.m_parentSiteMapFileCollection.Contains(this.m_siteMapFile))
                {
                    throw new InvalidOperationException(string.Format("文件{0}已被其他提供程序使用。", this.m_siteMapFile));
                }
                // 复制ParentProvider的文件路径信息
                foreach (string str in parentProvider.m_parentSiteMapFileCollection)
                {
                    this.m_parentSiteMapFileCollection.Add(str);
                }
            }
            this.m_parentSiteMapFileCollection.Add(this.m_siteMapFile);

            string physicalSiteMapFileName = HttpContext.Current.Server.MapPath(this.m_siteMapFile);
            var fileInfo = new FileInfo(physicalSiteMapFileName);
            string fileName = fileInfo.Name;
            // 设置文件监视
            this.m_fileHandler = new FileSystemWatcher();
            this.m_fileHandler.Path = fileInfo.DirectoryName;
            this.m_fileHandler.Filter = fileName;
            this.m_fileHandler.Changed += new FileSystemEventHandler(OnConfigFileChange);
            this.m_fileHandler.Created += new FileSystemEventHandler(OnConfigFileChange);
            this.m_fileHandler.Deleted += new FileSystemEventHandler(OnConfigFileChange);
            this.m_fileHandler.Renamed += new RenamedEventHandler(OnRenamed);
            this.m_fileHandler.EnableRaisingEvents = true;

            base.ResourceKey = fileName;

            lock (m_syncLock)
            {
                if (this.m_rootNode == null)
                {
                    this.Clear();

                    // 尝试从.sitamap的Xml文件中载入站点地图信息
                    //
                    XDocument siteMapXml = XDocument.Load(HttpContext.Current.Server.MapPath(this.m_siteMapFile));

                    // 获取XML格式sitemap文件的根节点元素
                    var elementRootNode = siteMapXml.Element(this.SiteMapNamespace + SITEMAP_ROOT_NODENAME);
                    if (elementRootNode == null)
                    {
                        throw new ConfigurationErrorsException(
                            string.Format("配置文件的顶层元素命名必须是 <{0}>。", SITEMAP_ROOT_NODENAME));
                    }

                    // 启用了本地化？
                    string enableLocalizationAttr = elementRootNode.GetAttributeValue("enableLocalization");
                    bool enableLocalization;
                    bool.TryParse(enableLocalizationAttr ?? "false", out enableLocalization);
                    EnableLocalization = enableLocalization;

                    // 获取根MvcSiteMapNode节点对象，通常为命名为“首页”的节点
                    //
                    var rootElement = elementRootNode.Element(this.SiteMapNamespace + SITEMAP_NODENAME);
                    if (rootElement == null)
                        throw new ConfigurationErrorsException(
                            string.Format("<{0}> 的子级具有不同于 <{1}> 的名称。", SITEMAP_ROOT_NODENAME, SITEMAP_NODENAME));

                    this.m_rootNode = GetSiteMapNodeFromXmlElement(rootElement, null);
                    this.m_rootNode.IsRootNode = true;
                    AddNode(m_rootNode, null);

                    ProcessXmlNodes(m_rootNode, rootElement);

                }
                return m_rootNode;
            }

        }

        /// <summary>
        /// 使用指定的 key 和用于本地化的显式的资源键来创建 MvcSiteMapNode 类的新实例。
        /// </summary>
        /// <param name="provider">与节点关联的 MvcSiteMapProvider。</param>
        /// <param name="explicitResourceKeys">用于本地化的显式资源键的 NameValueCollection。</param>
        /// <returns>一个MvcSiteMapNode 类的新实例。</returns>
        protected virtual MvcSiteMapNode CreateSiteMapNode(string key, NameValueCollection explicitResourceKeys)
        {
            return new MvcSiteMapNode(this, key, explicitResourceKeys);
        }

        /// <summary>
        /// 从一个sitemap文件中的XElement节点创建一个MvcSiteMapNode对象。
        /// </summary>
        /// <param name="node">用于创建MvcSiteMapNode对象的XElemen节点。</param>
        /// <param name="parentNode">创建该对象时，指定其父节点。</param>
        /// <returns>一个新的 MvcSiteMapNode 对象，
        /// 该对象的属性值从 <paramref name="node"/> 中的属性映射，同时指定了父节点。</returns>
        protected MvcSiteMapNode GetSiteMapNodeFromXmlElement(XElement node, MvcSiteMapNode parentNode)
        {
            var explicitResourceKeys = new NameValueCollection();
            var title = node.GetAttributeValue("title");
            var description = node.GetAttributeValue("description") ?? title;
            HandleResourceAttribute("title", ref title, ref explicitResourceKeys);
            HandleResourceAttribute("description", ref description, ref explicitResourceKeys);

            // 为该节点生成一个Key
            //string key = this.SiteMapNodeKeyGenerator.GenerateKey(
            string key = string.Join("_", new string[] {
                // node.GetAttributeValue("key"),
                node.GetAttributeValue("url"),
                //node.GetAttributeValue("title"),
                node.GetAttributeValue("area"),
                node.GetAttributeValue("controller"),
                node.GetAttributeValue("action")
                //(node.GetAttributeValue("clickable") == "false")
            });

            // 创建一个新的SiteMap节点，设置其中的Key和url。
            var siteMapNode = CreateSiteMapNode(key, explicitResourceKeys);
            siteMapNode.Title = title;
            siteMapNode.Description = description;

            IDictionary<string, object> routeValues = new Dictionary<string, object>();

            // 遍历每一个节点的属性，将其加入到属性集合和路由集合中
            // 
            foreach (XAttribute attribute in node.Attributes())
            {
                var attributeName = attribute.Name.ToString();
                var attributeValue = attribute.Value;

                if (attributeName != "title" && attributeName != "description")
                {
                    siteMapNode[attributeName] = attributeValue;
                }

                // Process route values
                if (attributeName == "area" || attributeName == "controller" || attributeName == "action")
                {
                    routeValues.Add(attributeName, attributeValue);
                }

                if (attributeName == "isMenu")
                {
                    siteMapNode.IsMenu = Convert.ToBoolean(attribute.Value);
                }

                if (attributeName == "titleOnly")
                {
                    siteMapNode.TitleOnly = Convert.ToBoolean(attribute.Value);
                }

                if (attributeName == "showMenu")
                    siteMapNode.ShowMenu = Convert.ToBoolean(attribute.Value);

                if (attributeName == "ignoreRole")
                    siteMapNode.IgnoreRole = Convert.ToBoolean(attribute.Value);
                
                if (attributeName == "roles")
                {
                    siteMapNode.Roles = attribute.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                }
            }

            // 从父节点继承 area 和 controller 
            var parentMvcNode = parentNode;
            if (parentMvcNode != null)
            {
                if (siteMapNode["area"] == null)
                {
                    siteMapNode["area"] = parentMvcNode.Area;
                    routeValues.Add("area", parentMvcNode.Area);
                }
                if (node.GetAttributeValue("controller") == "")
                {
                    siteMapNode["controller"] = parentMvcNode.Controller;
                    routeValues.Add("controller", parentMvcNode.Controller);
                }
            }

            // 为Area加入默认值
            if (!routeValues.ContainsKey("area"))
            {
                siteMapNode["area"] = "";
                routeValues.Add("area", "");
            }

            // 设置默认的 ISiteMapNodeUrlResolver实现
            if (siteMapNode.UrlResolver == null)
            {
                siteMapNode.UrlResolver = this.SiteMapNodeUrlResolver;
            }

            // 设置默认的ISiteMapNodeVisibilityProvider实现
            //
            if (siteMapNode.VisibilityProvider == null)
            {
                siteMapNode.VisibilityProvider = this.SiteMapNodeVisibilityProvider;
            }

            // 加入路由值到sitemap 节点
            siteMapNode.RouteValues = routeValues;
            routeValues = new Dictionary<string, object>();

            // 添加节点的路由Defaults值。
            var httpContext = new HttpContextWrapper(HttpContext.Current);
            var routeData = RouteTable.Routes.GetRouteData(httpContext);
            if (routeData != null)
            {
                // 指定siteMapNode的路由Defaults值。
                var route = routeData.Route as Route;
                if (route != null && route.Defaults != null)
                {
                    foreach (var defaultValue in route.Defaults)
                    {
                        if (siteMapNode[defaultValue.Key] == null)
                        {
                            siteMapNode[defaultValue.Key] = defaultValue.Value.ToString();
                        }
                    }
                }
            }

            return siteMapNode;
        }

        /// <summary>
        /// 从根目录开始递归查找sitemap文件的节点层次，
        /// 创建各节点的MvcSiteMapNode对象，并加入到当前的节点集合中。
        /// </summary>
        /// <param name="parentNode">用作父节点对象。</param>
        /// <param name="element">sitemap文档的节点。</param>
        protected void ProcessXmlNodes(MvcSiteMapNode parentNode, XElement element)
        {
            MvcSiteMapNode childNode = parentNode;

            foreach (XElement node in element.Elements())
            {
                if (node.Name == this.SiteMapNamespace + SITEMAP_NODENAME)
                {
                    childNode = GetSiteMapNodeFromXmlElement(node, parentNode);
                    AddNode(childNode, parentNode);
                }
                else
                {
                    throw new ConfigurationErrorsException(
                        string.Format("siteMap文件的子级具有不同于 <{0}> 的名称：{1}。", SITEMAP_NODENAME, node.Name));
                }

                ProcessXmlNodes(childNode, node);
            }
        }

        #region overrides

        /// <summary>
        /// 移除 MvcSiteMapProvider 作为其状态的一部分来跟踪的父和子站点地图节点的集合中的所有元素。
        /// </summary>
        protected override void Clear()
        {
            lock (m_syncLock)
            {
                this.m_rootNode = null;
                base.Clear();
            }
        }

        /// <summary>
        /// 使用指定的 <see cref="System.Web.HttpContext"/> 对象检索表示当前请求页的 <see cref="System.Web.SiteMapNode"/> 对象。
        /// </summary>
        /// <param name="context">用于使节点信息与被请求页的 URL 匹配的 <see cref="System.Web.HttpContext"/>。</param>
        /// <returns>
        /// 表示当前请求页的 <see cref="System.Web.SiteMapNode"/>；
        /// 如果在 <see cref="System.Web.SiteMapNode"/> 中未找到对应的 <see cref="System.Web.SiteMapNode"/>，
        /// 或者页上下文为 null，则为 null。
        /// </returns>
        public override SiteMapNode FindSiteMapNode(HttpContext context)
        {
            if (context == null)
                return null;

            MvcSiteMapNode node = null;

            HttpContextBase httpContextBase = new HttpContextWrapper(context);
            RouteData routeData = RouteTable.Routes.GetRouteData(httpContextBase);
            if (routeData != null)
            {
                RequestContext requestContext = new RequestContext(httpContextBase, routeData);
                VirtualPathData vpd = routeData.Route.GetVirtualPath(requestContext, routeData.Values);

                // 首先尝试以当前请求的Url查找
                // 因为.sitemap文件有些节点直接指定了Url
                //
                string appPathPrefix = (requestContext.HttpContext.Request.ApplicationPath ?? string.Empty).TrimEnd('/') + "/";
                node = base.FindSiteMapNode(requestContext.HttpContext.Response.ApplyAppPathModifier(appPathPrefix + vpd.VirtualPath)) as MvcSiteMapNode;

                if (!routeData.Values.ContainsKey("area"))
                {
                    if (routeData.DataTokens["area"] != null)
                    {
                        routeData.Values.Add("area", routeData.DataTokens["area"]);
                    }
                    else
                    {
                        routeData.Values.Add("area", "");
                    }
                }

                // 然后尝试从根节点开始递归查找
                //
                MvcSiteMapNode rootNode = RootNode as MvcSiteMapNode;

                // 如果是根节点
                if (NodeMatchesRoute(rootNode, routeData.Values))
                {
                    node = rootNode;
                }

                if (node == null)
                {
                    node = FindControllerActionNode(rootNode, routeData.Values);
                }
            }

            // 如果从当前的路由信息中没有查找到节点，则尝试从基类获取
            //
            if (node == null)
            {
                node = base.FindSiteMapNode(context) as MvcSiteMapNode;
            }

            if (node != null && node.IsAccessibleToUser(context))
            {
                return node;
            }
            return null;
        }

        /// <summary>
        /// 检索目前由当前提供者管理的所有节点的根节点。
        /// </summary>
        /// <returns>一个 MvcSiteMapNode，表示当前提供程序所管理的节点集的根节点。</returns>
        protected override SiteMapNode GetRootNodeCore()
        {
            return this.BuildSiteMap();
        }

        /// <summary>
        /// 初始化 <see cref="MvcSiteMapProvider" /> 实现，从持久存储区加载站点地图所需的任何资源。
        /// </summary>
        /// <param name="name"></param>
        /// <param name="attributes"></param>
        public override void Initialize(string name, NameValueCollection attributes)
        {
            if (this.m_initialized)
                throw new InvalidOperationException("尝试多次初始化 MvcSiteMapProvider。");

            base.Initialize(name, attributes);

            // 获取siteMapFile 文件路径
            if (!string.IsNullOrEmpty(attributes["siteMapFile"]))
                this.m_siteMapFile = attributes["siteMapFile"];

            // 启用本地化
            bool enableLocalization;
            bool.TryParse(attributes["enableLocalization"], out enableLocalization);
            EnableLocalization = enableLocalization;

            // 对应的本地化资源Key
            if (!string.IsNullOrEmpty(attributes["resourceKey"]))
            {
                ResourceKey = attributes["resourceKey"];
            }

            this.ControllerTypeResolver = this.SetProperty<IControllerTypeResolver>(attributes["controllerTypeResolver"], () => new DefaultControllerTypeResolver());
            //this.SiteMapNodeKeyGenerator = this.SetProperty<ISiteMapNodeKeyGenerator>(attributes["siteMapNodeKeyGenerator"], () => new DefaultSiteMapNodeKeyGenerator());
            this.SiteMapNodeUrlResolver = this.SetProperty<ISiteMapNodeUrlResolver>(attributes["siteMapNodeUrlResolver"], () => new DefaultSiteMapNodeUrlResolver());
            this.SiteMapNodeVisibilityProvider = this.SetProperty<ISiteMapNodeVisibilityProvider>(attributes["siteMapNodeVisibilityProvider"], () => new DefaultSiteMapNodeVisibilityProvider());
            this.ActionMethodParameterResolver = this.SetProperty<IActionMethodParameterResolver>(attributes["actionMethodParameterResolver"], () => new DefaultActionMethodParameterResolver());

            this.m_initialized = true;
        }

        #endregion

        #region IDisposable

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (this.m_fileHandler != null)
            {
                this.m_fileHandler.EnableRaisingEvents = false;
                this.m_fileHandler.Dispose();
            }
        }

        #endregion

        #region Helper Method

        /// <summary>
        /// 获取一个值，该值指示指定节点的属性值是否与当前请求的路由信息包含的属性值完全匹配。
        /// </summary>
        /// <param name="mvcNode">需要进行匹配的节点对象。</param>
        /// <param name="values">当前请求的路由信息。
        /// 该路由信息包含当前请求的controller名称和action名称等信息。</param>
        /// <returns>
        /// 如果该sitemap节点值与路由信息值完全匹配则返回 <see cref="bool"/>，否则返回 <see cref="false"/>。
        /// </returns>
        public bool NodeMatchesRoute(MvcSiteMapNode mvcNode, RouteValueDictionary values)
        {
            if (mvcNode == null)
                return false;

            // 首先查找该节点所指定的 action 方法的参数信息，用于与指定节点的属性进行匹配查找
            IEnumerable<string> actionParameters = ActionMethodParameterResolver.ResolveActionMethodParameters(
                ControllerTypeResolver, mvcNode.Area, mvcNode.Controller, mvcNode.Action);
            if (actionParameters == null)
                actionParameters = new List<string>();

            if (values.Count > 0)
            {
                foreach (var pair in values)
                {
                    // 遍历所有的路由信息，如果该路由信息能在该MvcSiteMapNode节点的属性中找到对应的值，
                    // 则说明该节点即为当前请求对应的节点。
                    if (mvcNode[pair.Key] != null)
                    {
                        if (mvcNode[pair.Key].ToLowerInvariant() == pair.Value.ToString().ToLowerInvariant())
                        {
                            continue;
                        }
                        else
                        {
                            // 如果该路由信息的属性名称和sitemap节点的属性名称相同但值不同时，
                            // 则查看该路由属性是否是action方法的参数
                            //
                            if (!actionParameters.Contains(pair.Key, StringComparer.InvariantCultureIgnoreCase))
                            {
                                return false;
                            }
                        }
                    }
                    else
                    {
                        // 如果路由信息的该属性值为空或为路由可选参数，
                        // 则忽略该参数值
                        if (pair.Value == null || string.IsNullOrEmpty(pair.Value.ToString()) || pair.Value == UrlParameter.Optional)
                        {
                            continue;
                        }
                    }
                }
                return true;
            }

            return false;
        }

        /// <summary>
        /// 从指定节点的所有子节点中递归查找完全匹配当前请求的路由信息的节点。
        /// </summary>
        /// <param name="rootNode">用于查找的sitemap节点对象。</param>
        /// <param name="values">当前请求的路由信息。
        /// 该路由信息包含当前请求的controller名称和action名称等信息。</param>
        /// <returns>
        /// 如果找到匹配的节点，一个与当前请求的路由信息值完全匹配的sitemap节点对象；否则返回null。
        /// </returns>
        public MvcSiteMapNode FindControllerActionNode(SiteMapNode node, RouteValueDictionary values)
        {
            if (node != null)
            {
                SiteMapNodeCollection childNodes = GetChildNodes(node);

                // 首先直接从第一层的子节点中查找
                foreach (SiteMapNode childNode in childNodes)
                {
                    var mvcNode = childNode as MvcSiteMapNode;
                    if (mvcNode != null)
                    {
                        if (NodeMatchesRoute(mvcNode, values))
                        {
                            return mvcNode;
                        }
                    }
                }

                // 然后再尝试查找下一层的子节点
                //
                foreach (SiteMapNode childNode in childNodes)
                {
                    var siteMapNode = FindControllerActionNode(childNode, values);
                    if (siteMapNode != null)
                    {
                        return siteMapNode;
                    }
                }
            }

            return null;
        }

        private MvcSiteMapNode NullIfNotVisible(MvcSiteMapNode node)
        {
            if (node != null && node.IsAccessibleToUser(HttpContext.Current))
                return node;
            return null;
        }

        /// <summary>
        /// 确保传入的参数 <paramref name="node"/> 类型为MvcSiteMapNode。
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        private MvcSiteMapNode EnsureMvcSiteMapNodeType(SiteMapNode node)
        {
            if (node == null)
                throw new ArgumentNullException("node");

            MvcSiteMapNode mvcNode = node as MvcSiteMapNode;
            if (mvcNode == null)
                throw new InvalidOperationException("只能对 MvcSiteMapNode 类型的节点进行操作。");

            return mvcNode;
        }

        private void OnConfigFileChange(object sender, FileSystemEventArgs e)
        {
            MvcSiteMapProvider parentProvider = this.ParentProvider as MvcSiteMapProvider;
            if (parentProvider != null)
            {
                parentProvider.OnConfigFileChange(sender, e);
            }
            this.Clear();
        }

        private void OnRenamed(object sender, RenamedEventArgs e)
        {
            MvcSiteMapProvider parentProvider = this.ParentProvider as MvcSiteMapProvider;
            if (parentProvider != null)
            {
                parentProvider.OnConfigFileChange(sender, e);
            }
            this.Clear();
        }

        /// <summary>
        /// 尝试从资源文件中获取文本值。
        /// </summary>
        /// <param name="attributeName">需要获取资源的属性名称。</param>
        /// <param name="text">从资源中获取到的文本值。</param>
        /// <param name="collection">NameValueCollection to be used for localization</param>
        private static void HandleResourceAttribute(string attributeName, ref string text, ref NameValueCollection collection)
        {
            if (!string.IsNullOrEmpty(text))
            {
                string tempStr1 = null;
                var tempStr2 = text.TrimStart(new[] { ' ' });
                if (((tempStr2 != null) && (tempStr2.Length > 10)) && tempStr2.ToLower(CultureInfo.InvariantCulture).StartsWith("$resources:", StringComparison.Ordinal))
                {
                    tempStr1 = tempStr2.Substring(11);
                    string tempStr3 = null;
                    string tempStr4 = null;
                    var index = tempStr1.IndexOf(',');
                    tempStr3 = tempStr1.Substring(0, index);
                    tempStr4 = tempStr1.Substring(index + 1);
                    var length = tempStr4.IndexOf(',');
                    if (length != -1)
                    {
                        text = tempStr4.Substring(length + 1);
                        tempStr4 = tempStr4.Substring(0, length);
                    }
                    else
                    {
                        text = null;
                    }
                    if (collection == null)
                    {
                        collection = new NameValueCollection();
                    }
                    collection.Add(attributeName, tempStr3.Trim());
                    collection.Add(attributeName, tempStr4.Trim());
                }
            }
        }

        private T SetProperty<T>(string typeName, Func<T> defaultInstance)
        {
            if (!string.IsNullOrEmpty(typeName))
                return CreateTypeInstance<T>(typeName);
            else
                return defaultInstance();
        }

        internal static T CreateTypeInstance<T>(string typeName)
        {
            Type t = Type.GetType(typeName);
            if (t != null)
                return (T)Activator.CreateInstance(t);

            throw new InvalidOperationException("不能从指定的类型名称'" + typeName + "'创建类型。");
        }

        #endregion

    }
}
